﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ERPStore.Extensions;
using ERPStore.Cart.Repositories;

using CloudSoft.Extensions;

using Microsoft.Practices.Unity;

namespace ERPStore.Cart.Services
{
	/// <summary>
	/// Service de gestion des paniers
	/// </summary>
	public class CartService : ERPStore.Services.ICartService 
	{
		private SynchronizedCollection<ERPStore.Models.CartItem> m_LastCartItems;

		public CartService(Logging.ILogger logger
			, Repositories.ICartRepository cartRepository
			, ERPStore.Services.ICatalogService catalogService
			, ERPStore.Services.IAccountService accountService
			, EventBrokR.IPublisher eventPublisherService
			, ERPStore.Services.ICommentService commentService
			, ERPStore.Services.ISalesService salesService
			, ERPStore.Services.IFraudService fraudService
			)
		{
			m_LastCartItems = new System.Collections.Generic.SynchronizedCollection<ERPStore.Models.CartItem>();

			this.Logger = logger;
			this.CartRepository = cartRepository;
            this.CatalogService = catalogService;
			this.AccountService = accountService;
			this.EventPublisherService = eventPublisherService;
			this.CommentService = commentService;
			this.SalesService = salesService;
			this.FraudService = fraudService;
		}

		protected Logging.ILogger Logger { get; private set; }
		protected Repositories.ICartRepository CartRepository { get; private set; }
		protected ERPStore.Services.ICatalogService CatalogService { get; private set; }
		protected ERPStore.Services.IAccountService AccountService { get; private set; }
		protected EventBrokR.IPublisher EventPublisherService { get; private set; }
		protected ERPStore.Services.ISalesService SalesService { get; private set; }
		protected ERPStore.Services.ICommentService CommentService { get; private set; }
		protected ERPStore.Services.IFraudService FraudService { get; private set; }

		public DateTime Ping()
		{
			return DateTime.Now;
		}

		public virtual ERPStore.Models.OrderCart GetOrCreateCart(ERPStore.Models.UserPrincipal user)
		{
			if (user.VisitorId.IsNullOrTrimmedEmpty())
			{
				throw new ArgumentException("Visitor does not be null or empty");
			}
			var cart = GetCurrentCart(user);
			if (cart != null)
			{
				return cart;
			}

			cart = CreateCart(user);
			return cart;
		}


		public virtual ERPStore.Models.OrderCart GetCurrentCart(ERPStore.Models.UserPrincipal user)
		{
			var cart = CartRepository.GetCartList().Current().GetByUserPrincipal(user).FirstOrDefault().ToOrderCartModel();
			return cart;
		}

		public virtual ERPStore.Models.OrderCart GetCurrentCart(string visitorId)
		{
			var cart = CartRepository.GetCartList().Current().GetByVisitorId(visitorId).FirstOrDefault().ToOrderCartModel();
			return cart;
		}

		public virtual ERPStore.Models.OrderCart CreateCart(ERPStore.Models.UserPrincipal principal)
		{
			if (principal.VisitorId.IsNullOrTrimmedEmpty())
			{
				throw new ArgumentException("Visitor does not be null or empty");
			}

			var cart = new ERPStore.Models.OrderCart();
			cart.VisitorId = principal.VisitorId;
			cart.Conveyor = AccountService.GetDefaultConveyor(); 
			cart.AllowPartialDelivery = ERPStore.GlobalConfiguration.Configuration.Settings.Shipping.AllowPartialDelivery;
			cart.CreationDate = DateTime.Now;
			cart.ShippingFeeLocked = false;
			if (principal.CurrentUser != null)
			{
				cart.CustomerId = principal.CurrentUser.Id;
			}

			return cart;
		}

		public virtual ERPStore.Models.OrderCart CreateCart(ERPStore.Models.OrderCart cart, ERPStore.Models.UserPrincipal principal)
		{
			var c = new ERPStore.Models.OrderCart();
			c.AcceptCondition = false;
			c.AcceptConversion = false;
			c.AllowPartialDelivery = cart.AllowPartialDelivery;
			c.BillingAddress = null;
			c.CreationDate = DateTime.Now;
			c.DiscountTotal = cart.DiscountTotal;
			c.Message = cart.Message;
			c.ShippingFee = cart.ShippingFee;
			c.VisitorId = principal.VisitorId;
			c.ShippingFeeLocked = cart.ShippingFeeLocked;

			foreach (var item in cart.Items)
			{
				var i = new ERPStore.Models.CartItem();
				i.Packaging = item.Packaging;
				i.Product = item.Product;
				i.Quantity = item.Quantity;
				i.RecyclePrice = item.RecyclePrice;
				i.SalePrice = item.SalePrice;
				i.SaleUnitValue = item.SaleUnitValue;
				i.CreationDate = DateTime.Now;
				i.LastUpdate = DateTime.Now;
				i.Discount = item.Discount;
				i.CatalogPrice = item.CatalogPrice;
				i.AllowPartialDelivery = item.AllowPartialDelivery;
				i.IsLocked = item.IsLocked;
				i.PriceType = item.PriceType;
				i.ProductStockInfo = item.ProductStockInfo;
				c.Items.Add(i);
			}
			return c;
		}

		public virtual ERPStore.Models.OrderCart CreateAndSaveCart(ERPStore.Models.UserPrincipal user)
		{
			var cart = CreateCart(user);
			CartRepository.Save(cart);
			return cart;
		}

		public virtual ERPStore.Models.OrderCart GetCartById(int id)
		{
			return CartRepository.GetCartList().GetById(id).ToOrderCartModel();
		}

		public virtual ERPStore.Models.OrderCart GetCartByCode(string cartCode)
		{
			var data = CartRepository.GetCartList().GetByCode(cartCode);
			return data.ToOrderCartModel();
		}

		public virtual ERPStore.Models.OrderCart GetCurrentCartByCode(string cartCode)
		{
			return CartRepository.GetCartList().Current().GetByCode(cartCode).ToOrderCartModel();
		}

		public virtual IList<ERPStore.Models.OrderCart> GetActiveCartList(ERPStore.Models.UserPrincipal user)
		{
			if (user.VisitorId.IsNullOrTrimmedEmpty())
			{
				throw new ArgumentException("Visitor does not be null or empty");
			}

			var list = from data in CartRepository.GetCartList().GetByUserPrincipal(user).GetActive().ToList()
					   select data.ToOrderCartModel();

			return list.ToList();
		}

		public virtual void RemoveCart(ERPStore.Models.OrderCart cart)
		{
			Logger.Info("Delete ordercart {0}", cart.Code);
			CartRepository.Remove(cart.Code);
		}

		public virtual void AddCartItem(ERPStore.Models.OrderCart cart, ERPStore.Models.Product product, int quantity, string offerName, string ip, string addFrom)
		{
			// TODO : Placer ces règles d'ajout dans SalesService
			// checker la possibilité d'ajout
			if (product.SaleMode == ERPStore.Models.ProductSaleMode.NotSellable)
			{
				// TODO : Convert to throw exception
				Logger.Warn("Add not sellable product {0} in order cart", product.Code);
				return;
			}

			string reason = null;
			if (FraudService.IsBlackListed(cart.VisitorId, ip, out reason))
			{
				Logger.Notification(reason);
				return;
			}

			quantity = Math.Max(1, quantity);
			var existing = cart.Items.FirstOrDefault(i => i.Product.Id == product.Id);
			var salePrice = GetPriceByQuantity(product, quantity, product.BestPrice);

			var productStockInfo = CatalogService.GetProductStockInfo(product);

			if (product.SaleMode == ERPStore.Models.ProductSaleMode.EndOfLife)
			{
				if (productStockInfo == null)
				{
					// TODO : Convert to throw exception
					Logger.Warn("Add end of life product {0} in order cart", product.Code);
					return;
				}

				var q = quantity;
				if (existing != null)
				{
					q = existing.Quantity + quantity;
				}
				if (productStockInfo.AvailableStock < q)
				{
					// TODO : Convert to throw exception
					Logger.Warn("Add end of life product {0} in order cart , quantity {1}", product.Code, quantity);
					return;
				}
			}

			if (existing == null)
			{
				Logger.Info("Add product {0} quantity {1} cart {2}", product.Code, quantity, cart.Code);

				var cartItem = CreateCartItem();
				cartItem.Product = product;
				cartItem.Quantity = quantity;
				cartItem.SalePrice = salePrice;
				cartItem.SaleUnitValue = product.SaleUnitValue;
				cartItem.Packaging = product.Packaging.Value;
				cartItem.RecyclePrice = product.RecyclePrice;
				cartItem.OfferName = offerName;
				cartItem.AddFrom = addFrom;
				if (productStockInfo == null)
				{
					cartItem.InventoryTimeStateId = -4; // Never in stock

				}
				else if (productStockInfo.AvailableStock > quantity)
				{
					cartItem.InventoryTimeStateId = -1; // Available
				}
				else if (productStockInfo.HasStock)
				{
					cartItem.InventoryTimeStateId = -2; // PArtialyAvailable
				}
				else
				{
					cartItem.InventoryTimeStateId = -3; // Not available
				}


				cart.Items.Add(cartItem);

				var trace = DateTime.Now;

                EventPublisherService.PublishAsync(new Events.AddCartItemEvent()
                {
                    Product = cartItem.Product,
                    Quantity = cartItem.Quantity,
                    SalePrice = cartItem.SalePrice,
					VisitorId = cart.VisitorId,
                }, 20 * 1000).ContinueWith(task =>
					{
						Console.WriteLine("duration {0}", (DateTime.Now - trace).TotalSeconds);
					});

				AddLastCartItem(cartItem);
			}
			else if (!existing.IsLocked)
			{
				Logger.Info("Update cartitem product {0} quantity {1} cart {2}", product.Code, quantity, cart.Code);
				existing.Quantity += quantity;
			}
		}

		public virtual void RecalcCartItem(ERPStore.Models.CartItem item, int quantity)
		{
			if (item.IsLocked)
			{
				return;
			}
			item.Quantity = quantity;
			item.SalePrice = GetPriceByQuantity(item.Product, quantity, item.SalePrice);
		}

		public virtual void RemoveCartItem(ERPStore.Models.OrderCart cart, int index)
		{
			if (cart == null)
			{
				return;
			}
			if (index >= cart.ItemCount)
			{
				return;
			}
			var item = cart.Items[index];
			Logger.Info("Delete cartitem product {0} cart {1}", item.Product.Code, cart.Code);
			cart.Items.Remove(item);
		}

		public virtual void Clear(ERPStore.Models.OrderCart cart)
		{
			if (cart == null)
			{
				return;
			}
			Logger.Info("Empty cart {0}", cart.Code);
			cart.Items.Clear();
		}

		public virtual void Save(ERPStore.Models.OrderCart cart)
		{
			if (cart == null)
			{
				return;
			}

			bool isNewCart = false;
			if (cart.Id == 0)
			{
				isNewCart = true;
			}

			CartRepository.Save(cart);

			if (isNewCart)
			{
				var eventMessage = new Events.OrderCartCreatedEvent()
				{
					CartCode = cart.Code,
					VisitorId = cart.VisitorId,
					LeadSourceId = cart.LeadSourceId,
				};
				EventPublisherService.PublishAsync(eventMessage);
			}
		}

		public virtual void ChangeCurrentCart(string cartId, ERPStore.Models.UserPrincipal principal)
		{
			var list = CartRepository.GetCartList().GetByUserPrincipal(principal).GetActive().ToList();
			foreach (var cart in list)
			{
				if (cart.Code.Equals(cartId, StringComparison.InvariantCultureIgnoreCase))
				{
					cart.CartStateId = (int)ERPStore.Models.CartState.CurrentOrder;
				}
				else if (cart.CartStateId == (int)ERPStore.Models.CartState.CurrentOrder)
				{
					cart.CartStateId = (int)ERPStore.Models.CartState.Order;
				}
				var model = cart.ToOrderCartModel();
				CartRepository.Save(model);
			}
		}

		public virtual void DeleteCart(string cartId, ERPStore.Models.UserPrincipal user)
		{
			var cart = CartRepository.GetCartList().GetByUserPrincipal(user).GetActive().GetByCode(cartId);
			if (cart == null)
			{
				return;
			}

			CartRepository.Remove(cartId);
		}

		public virtual void AddCart(ERPStore.Models.OrderCart cart, ERPStore.Models.UserPrincipal principal)
		{
			CartRepository.Save(cart);
		}

		public ERPStore.Models.OrderCart GetOrderCartByConvertedEntityId(int entityId, ERPStore.Models.UserPrincipal user)
        {
			var cart = CartRepository.GetCartList().GetByConvertedEntityId(entityId);
			return cart.ToOrderCartModel();
        }

		public IEnumerable<ERPStore.Models.CartItem> GetLastCartItem(int itemCount)
		{
			if (m_LastCartItems.IsNullOrEmpty())
			{
				var list = CartRepository.GetLastCartItem(itemCount);
				lock (m_LastCartItems.SyncRoot)
				{
					foreach (var item in list)
					{
						m_LastCartItems.Add(item);
					}
				}
			}

			return m_LastCartItems.Take(itemCount);
		}

		public ERPStore.Models.CartItem CreateCartItem()
		{
			var result = new ERPStore.Models.CartItem();
			// result.Id = Guid.NewGuid().ToString();
			result.CreationDate = DateTime.Now;
			result.LastUpdate = DateTime.Now;
			result.Discount = 0;
			result.PriceType = ERPStore.Models.PriceType.Normal;
			result.ShippingType = ERPStore.Models.ShippingType.WhenAvailable;
			result.AllowPartialDelivery = false;

			return result;
		}

		public IEnumerable<ERPStore.Models.Comment> GetCommentListByCart(ERPStore.Models.OrderCart cart)
		{
			if (cart == null)
			{
				return null;
			}
			var list = CommentService.GetCommentList(cart);

			return list;
		}

		public string GetCurrentCartHref(ERPStore.Models.OrderCart cart)
		{
			// var url = helper.RouteERPStoreUrl(Routes.CART_SHOW, new { cartid = cart.Code });
			return string.Format("/panier/voir/{0}", cart.Code);
		}

		public void ApplyProductStockInfoList(ERPStore.Models.OrderCart cart)
		{
			// Guard
			if (cart == null
				|| cart.Items.IsNullOrEmpty())
			{
				return;
			}

			// On recherche la liste des infos de stock
			var productIdList = cart.Items.Select(i => i.Product.Id).Distinct();
			var list = CatalogService.GetProductStockInfoList(productIdList);

			foreach (var item in cart.Items)
			{
				var psi = list.SingleOrDefault(i => i.ProductCode.Equals(item.Product.Code, StringComparison.InvariantCultureIgnoreCase));
				item.ProductStockInfo = psi;
			}
		}


		private void AddLastCartItem(ERPStore.Models.CartItem cartItem)
		{
			// Conservation des 20 derniers dans la liste
			lock (m_LastCartItems.SyncRoot)
			{
				if (!m_LastCartItems.Any(i => i.Product.Id == cartItem.Product.Id))
				{
					m_LastCartItems.Insert(0, cartItem);

					if (m_LastCartItems.Count > 40)
					{
						m_LastCartItems.RemoveAt(40);
					}
				}
			}
		}

		private ERPStore.Models.Price GetPriceByQuantity(ERPStore.Models.Product product, int quantity, ERPStore.Models.Price defaultSalePrice)
		{
			var result = defaultSalePrice;
			if (product.PriceByQuantityList.IsNotNullOrEmpty())
			{
				var price = product.PriceByQuantityList.First(i => quantity >= i.From
																&& quantity < i.To.GetValueOrDefault(int.MaxValue));
				if (price != null)
				{
					result = price.SalePrice;
				}
			}
			return result;
		}

	}
}
